﻿using System;
using System.Collections.Generic;
using Amib.Threading;
using log4net;
using Quartz;

namespace CML.Intercamber.Service.TaskManager
{
    /// <summary>
    /// Configs for BaseClusteredTask
    /// </summary>
    public static class BaseClusteredTaskConfiguration
    {
        public static Dictionary<string, IntercambersTasksConfigElement> Configurations = null;
    }

    /// <summary>
    /// Classe pour gérer les locks sur les jobs BaseClusteredTask
    /// </summary>
    public class BaseClusteredTaskLocker
    {
        /// <summary>
        /// Pour gérer un singleton sur l'execute (quartz ne peut pas lancer en parallèle plusieurs instances de ce job)
        /// Contient la liste des instances de BaseClusteredTaskLocker pour chaque tache 
        /// </summary>
        public static Dictionary<string, BaseClusteredTaskLocker> LockerInstances = new Dictionary<string, BaseClusteredTaskLocker>();

        public object ObjectLocker = new Object();
        public bool IsRunning = false;
        public DateTime? LastRunSuccessDate = null;

        /// <summary>
        /// Pour un taskName donné, on récupère la date de dernier run en succès (meme si ce dernier run n'a rien traité)
        /// </summary>
        /// <param name="className"></param>
        /// <returns></returns>
        public static DateTime? TaskLastSuccessTime(string className)
        {
            if (LockerInstances.ContainsKey(className))
                return LockerInstances[className].LastRunSuccessDate;
            return null;
        }
    }

    /// <summary>
    /// Ce template de job quartz permet de définir une tache qui va s'executer en 2 temps : 
    /// 1) récupération d'une liste de paramètres de micro jobs (fonction PrepareMicroJobTraitement)
    /// 2) run des micro jobs pour chacuns des éléments de cette liste en parallèle sur N threads (fonction MicroJobTraitement)
    /// La fonction MicroJobTraitement peut mettre a jour l'object passé en paramètre, et la liste de tous ces objects traités sont passés en 
    /// paramètres de JobPostTraitement
    /// </summary>
    /// <typeparam name="T">classe représentant les paramètres qui seront passés au microjob</typeparam>
    public abstract class BaseClusteredTask<T> : IJob
    {

        #region variables globales

        /// <summary>
        /// cache pour le nom de la classe de l'instance
        /// </summary>
        private string currentTypeName;

        protected readonly ILog Logger = null;

        /// <summary>
        /// setté par le gestionnaire de conf
        /// </summary>
        public int MaxWorkerThreads = 3;

        /// <summary>
        /// Nombre d'items à traiter par thread
        /// </summary>
        public int ItemsPerThread = 50;

        #endregion

        #region constructeur

        protected BaseClusteredTask()
        {
            Type myType = this.GetType();
            Logger = LogManager.GetLogger(myType);

            // init MaxWorkerThreads from conf
            if (BaseClusteredTaskConfiguration.Configurations.ContainsKey(myType.ToString()))
            {
                IntercambersTasksConfigElement conf = BaseClusteredTaskConfiguration.Configurations[myType.ToString()];
                this.MaxWorkerThreads = conf.ThreadNumbers;
                this.ItemsPerThread = conf.ItemsPerThread;
            }
        }
        #endregion

        #region Gestion de la concurence
        /// <summary>
        /// Obtient le locker associé à ce job
        /// </summary>
        private BaseClusteredTaskLocker GetLocker
        {
            get
            {
                string className = this.GetType().ToString();

                if (!BaseClusteredTaskLocker.LockerInstances.ContainsKey(className))
                    BaseClusteredTaskLocker.LockerInstances.Add(className, new BaseClusteredTaskLocker());

                return BaseClusteredTaskLocker.LockerInstances[className];
            }
        }
        #endregion

        #region methode Execute de Quartz

        /// <summary>
        /// utilise SmartThreadPool (http://www.codeproject.com/Articles/7933/Smart-Thread-Pool) 
        /// </summary>
        public void Execute(IJobExecutionContext context)
        {
            // On ne permet qu'une seule execution parallèle du job quartz. 
            // Si le run précédent n'est pas terminé, on ne fait rien, et c'est le suivant qui sera (peut etre) executé
            BaseClusteredTaskLocker locker = this.GetLocker;
            if (!locker.IsRunning)
                lock (locker.ObjectLocker)
                {
                    locker.IsRunning = true;
                    currentTypeName = this.GetType().Name;
                    bool success = false;

                    Logger.Info(FormatTaskLogMessage("Demarrage du job"));

                    List<T> jobParams = PrepareMicroJobTraitement();
                    if (jobParams == null)
                        jobParams = new List<T>();
                    Logger.Info(FormatTaskLogMessage("Nombre de micro jobs identifies: " + jobParams.Count));

                    if (jobParams.Count > 0)
                    {
                        SmartThreadPool threadPool = new SmartThreadPool(60, MaxWorkerThreads);
                        IWaitableResult[] threadResults = new IWaitableResult[jobParams.Count];
                        for (int i = 0; i < jobParams.Count; i++)
                        {
                            T jobParam = jobParams[i];
                            threadResults[i] = threadPool.QueueWorkItem(() => MicroJobTraitement(jobParam));
                        }
                        success = SmartThreadPool.WaitAll(threadResults);
                    }

                    JobPostTraitement(success, jobParams);

                    Logger.Info(FormatTaskLogMessage("Fin du job"));
                    if (success)
                        locker.LastRunSuccessDate = DateTime.Now;
                    locker.IsRunning = false;
                }
        }
        #endregion

        #region A utiliser pour les logs !

        /// <summary>
        /// A utiliser pour les logs dans les classes qui héritent !!
        /// </summary>
        protected string FormatTaskLogMessage(string message)
        {
            return String.Format("[{0}] {1}", new string[2] { this.currentTypeName, message });
        }

        #endregion

        #region fonction à implémenter

        /// <summary>
        /// A implémenter : fonction récupère la liste des objects à traiter
        /// </summary>
        /// <returns>
        /// la liste des éléments représentant un paramètre de micro job. 
        /// Cette liste doit avoir au maximum this.ItemsPerThread éléments
        /// </returns>
        public abstract List<T> PrepareMicroJobTraitement();

        /// <summary>
        /// A implémenter : logique de traitement du microjob
        /// </summary>
        /// <param name="jobParam">Conf du microjob</param>
        public abstract void MicroJobTraitement(T jobParam);

        /// <summary>
        /// A implémenter : traitements effectués à la fin du job 
        /// </summary>
        /// <param name="success">true si tous les micro traitements sont ok, false sinon</param>
        /// <param name="jobParams">List des paramètres des sous jobs</param>
        public virtual void JobPostTraitement(bool success, List<T> jobParams)
        {
            Logger.Info(FormatTaskLogMessage("Jobs status: " + (success ? "succes" : "echec")));
        }

        #endregion

    }
}
